package com.mgy.example.service.common.lock;

import com.mgy.example.constants.Constant;
import com.mgy.example.service.common.config.redis.RedisMessageReceiver;
import com.mgy.example.service.common.config.redis.RedisMessageSender;
import com.mgy.example.service.common.config.redis.RedisSemaphore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class RedisLockService {

    @Autowired(required = false)
    private RedisTemplate redisTemplate;
    @Autowired
    private RedisMessageSender redisMessageSender;
    @Autowired
    private RedisMessageReceiver redisMessageReceiver;
    @Autowired
    private RedisSemaphore redisSemaphore;
    /**
     * lua脚本， 返回的result需要用tonumber方法转换类型
     */
    private static final String SET_IF_ABSENT_SCRIPT = "local  result= redis.call('setNX',KEYS[1],ARGV[1]) ; " +
            "if tonumber(result)==1 then  redis.call('expire',KEYS[1],ARGV[2])  return 1;  else  return 0; end;";

    /**
     * lua脚本
     */
    private static final String DELETE_IF_ABSENT_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    private RedisScript<Boolean> setIfAbsentScript = new DefaultRedisScript<>(SET_IF_ABSENT_SCRIPT, Boolean.class);

    private RedisScript<Boolean> delIfAbsentScript = new DefaultRedisScript<>(DELETE_IF_ABSENT_SCRIPT, Boolean.class);

    /**
     * 设置等待获取锁的超时时间，单位秒
     */
    public static final long WAIT_LOCK_TIMEOUT = 30;
    /**
     * 设置锁超时时间，超过时间缓存失效，单位秒
     */
    public static final long LOCK_TIMEOUT = 30;

    /**
     * 如果key值存在，则返回false，不更新值；如果key值不存在则缓存，返回true
     *
     * @param key     键
     * @param value   值
     * @param seconds 超时时间 单位秒
     * @return
     */
    private Boolean setIfAbsent(String key, String value, Long seconds) {
        List<Object> keys = new ArrayList<>();
        keys.add(key);
        //seconds不能转换为字符串类型，否则报错
        Object[] args = {value, seconds};
        //此处不强转类型编译报错
        return (Boolean) redisTemplate.<Boolean>execute(setIfAbsentScript, keys, args);
    }

    /**
     * 如果缓存值和value相同则删除缓存并且返回true,如果缓存不存在或缓存值和value不相同则直接返回false，不删除缓存
     *
     * @param key   键
     * @param value 值
     * @return true:删除成功,false:删除失败
     */
    private Boolean delIfAbsent(String key, String value) {
        List<Object> keys = new ArrayList<>();
        keys.add(key);
        Object[] args = {value};
        return (Boolean) redisTemplate.<Boolean>execute(delIfAbsentScript, keys, args);
    }


    /**
     * 加锁，
     *
     * @param key   缓存key
     * @param value 缓存值
     * @return true:获得锁，false：在指定的时间内没有获取到锁
     * @throws Exception Exception
     */
    public void tryLock(String key, String value) throws Exception {
        this.tryLock(key, value, WAIT_LOCK_TIMEOUT);
    }

    /**
     * 加锁，
     *
     * @param key             缓存key
     * @param value           缓存值
     * @param waitLockTimeout 设置等待获取锁的超时时间，单位秒
     * @return true:获得锁，false：在指定的时间内没有获取到锁
     * @throws Exception Exception
     */
    public void tryLock(String key, String value, long waitLockTimeout) throws Exception {
        Boolean isSuccess = false;
        //获取当前时间的毫秒数
        long millis = System.currentTimeMillis() + waitLockTimeout * 1000;
        while (System.currentTimeMillis() < millis) {
            isSuccess = this.setIfAbsent(key, value, LOCK_TIMEOUT);
            if (isSuccess) {
                break;
            }
            Thread.sleep(100);
        }
        if (!isSuccess) {
            throw new Exception("加锁等待超时");
        }
    }

    /**
     * 解锁
     *
     * @param key   缓存key
     * @param value 缓存值
     * @return true:解锁成功，fale：解锁失败
     */
    public void unlock(String key, String value) {
        this.delIfAbsent(key, value);
    }

    public void tryLockNew(String key, String value) throws Exception {
        this.tryLockNew(key, value, WAIT_LOCK_TIMEOUT);
    }


    /**
     * 使用redis的发布订阅消息 PUBLISH  publish/subscribe
     */
    public void tryLockNew(String key, String value, long waitLockTimeout) throws Exception {
        Boolean isSuccess = false;
        //获取当前时间的毫秒数
        long millis = System.currentTimeMillis() + waitLockTimeout * 1000;
        while (System.currentTimeMillis() < millis) {
            isSuccess = this.setIfAbsent(key, value, LOCK_TIMEOUT);
            if (isSuccess) {
                System.out.println("线程获得锁：" + Thread.currentThread().getName());
                break;
            }
            //等待获取许可
            redisSemaphore.acquire();
        }
        if (!isSuccess) {
            throw new Exception("加锁等待超时");
        }

    }


    public void unlockNew(String key, String value) {
        this.delIfAbsent(key, value);
        //发布释放锁成功的消息
        redisMessageSender.sendMessage(Constant.UNLOCK_SUCCESS);
    }

}
